// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.linqingying.gitcode.pullrequest.data

import com.intellij.collaboration.ui.codereview.details.data.CodeReviewCIJob
import com.intellij.collaboration.ui.codereview.details.data.CodeReviewCIJobState
import com.linqingying.gitcode.api.data.GCCommitCheckSuiteConclusion
import com.linqingying.gitcode.api.data.GCCommitStatusContextState
import com.linqingying.gitcode.api.data.GCRefUpdateRule
import com.linqingying.gitcode.api.data.pullrequest.GCPullRequestMergeStateStatus
import com.linqingying.gitcode.api.data.pullrequest.GCPullRequestMergeabilityData
import com.linqingying.gitcode.api.data.pullrequest.GCPullRequestMergeableState
import com.linqingying.gitcode.pullrequest.data.GCPRMergeabilityState.ChecksState
import kotlin.collections.buildList

class GCPRMergeabilityStateBuilder(private val headRefOid: String, private val prHtmlUrl: String,
                                   private val mergeabilityData: GCPullRequestMergeabilityData) {

  private var canOverrideAsAdmin = false
  private var requiredContexts = emptyList<String>()
  private var isRestricted = false
  private var requiredApprovingReviewsCount = 0

  fun withRestrictions(currentUserIsAdmin: Boolean, refUpdateRule: GCRefUpdateRule) {
    // TODO: load via PullRequest.viewerCanMergeAsAdmin when we update the min version
    canOverrideAsAdmin = /*baseBranchProtectionRules.enforceAdmins?.enabled == false &&*/currentUserIsAdmin
    requiredContexts = refUpdateRule.requiredStatusCheckContexts.filterNotNull()
    isRestricted = !refUpdateRule.viewerCanPush
    requiredApprovingReviewsCount = refUpdateRule.requiredApprovingReviewCount ?: 0
  }

  fun build(): GCPRMergeabilityState {
    val hasConflicts = when (mergeabilityData.mergeable) {
      GCPullRequestMergeableState.MERGEABLE -> false
      GCPullRequestMergeableState.CONFLICTING -> true
      GCPullRequestMergeableState.UNKNOWN -> null
    }

    val lastCommit = mergeabilityData.commits.nodes.lastOrNull()?.commit
    val contexts = lastCommit?.status?.contexts.orEmpty()
    val contextsCI = contexts.map { context ->
      CodeReviewCIJob(context.context, context.state.toCiState(), context.isRequired, context.targetUrl)
    }
    val checkSuites = lastCommit?.checkSuites?.nodes.orEmpty()
    val checkSuitesCI = checkSuites.flatMap { checkSuite -> checkSuite.checkRuns.nodes }.map { checkRun ->
      CodeReviewCIJob(checkRun.name, checkRun.conclusion.toCiState(), checkRun.isRequired, checkRun.detailsUrl ?: checkRun.url)
    }
    val ciJobs = buildList<CodeReviewCIJob> {
      contextsCI.forEach(::add)
      checkSuitesCI.forEach(::add)
    }

    val canBeMerged = when {
      mergeabilityData.mergeStateStatus.canMerge() -> true
      mergeabilityData.mergeStateStatus.adminCanMerge() && canOverrideAsAdmin -> true
      else -> false
    }

    val checksState = when (mergeabilityData.mergeStateStatus) {
      GCPullRequestMergeStateStatus.CLEAN,
      GCPullRequestMergeStateStatus.DIRTY,
      GCPullRequestMergeStateStatus.DRAFT,
      GCPullRequestMergeStateStatus.HAS_HOOKS,
      GCPullRequestMergeStateStatus.UNKNOWN,
      GCPullRequestMergeStateStatus.UNSTABLE -> null
      GCPullRequestMergeStateStatus.BEHIND -> ChecksState.BLOCKING_BEHIND
      GCPullRequestMergeStateStatus.BLOCKED -> {
        if (requiredContexts.isEmpty()
            || contexts
              .filter { it.state == GCCommitStatusContextState.SUCCESS }
              .map { it.context }
              .containsAll(requiredContexts)) {
          null
        }
        else ChecksState.BLOCKING_FAILING
      }
    }

    val actualRequiredApprovingReviewsCount =
      if (mergeabilityData.mergeStateStatus == GCPullRequestMergeStateStatus.BLOCKED && !isRestricted && checksState != ChecksState.BLOCKING_FAILING)
        requiredApprovingReviewsCount
      else 0

    return GCPRMergeabilityState(headRefOid, prHtmlUrl,
                                 hasConflicts,
                                 ciJobs,
                                 canBeMerged, mergeabilityData.canBeRebased,
                                 isRestricted, actualRequiredApprovingReviewsCount)
  }

  private fun GCCommitStatusContextState.toCiState(): CodeReviewCIJobState {
    return when (this) {
      GCCommitStatusContextState.ERROR,
      GCCommitStatusContextState.EXPECTED,
      GCCommitStatusContextState.FAILURE -> CodeReviewCIJobState.FAILED
      GCCommitStatusContextState.PENDING -> CodeReviewCIJobState.PENDING
      GCCommitStatusContextState.SUCCESS -> CodeReviewCIJobState.SUCCESS
    }
  }

  private fun GCCommitCheckSuiteConclusion?.toCiState(): CodeReviewCIJobState {
    return when (this) {
      null -> CodeReviewCIJobState.PENDING
      GCCommitCheckSuiteConclusion.ACTION_REQUIRED,
      GCCommitCheckSuiteConclusion.CANCELLED,
      GCCommitCheckSuiteConclusion.NEUTRAL,
      GCCommitCheckSuiteConclusion.STALE,
      GCCommitCheckSuiteConclusion.STARTUP_FAILURE,
      GCCommitCheckSuiteConclusion.TIMED_OUT,
      GCCommitCheckSuiteConclusion.FAILURE -> CodeReviewCIJobState.FAILED
      GCCommitCheckSuiteConclusion.SKIPPED -> CodeReviewCIJobState.SKIPPED
      GCCommitCheckSuiteConclusion.SUCCESS -> CodeReviewCIJobState.SUCCESS
    }
  }
}
